package com.spring.sd;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.Objects;

/**
 * 后置处理器的排序
 */

@Slf4j
public class BeanPostProcessorSort {


    public static void main(String[] args) {

        // 1、需要使用到 BeanFactory 的一个实现类： DefaultListableBeanFactory。有了 Bean 工厂，还需要定义 Bean，之后再把定义的 Bean 注册到工厂即可。
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 2、尝试将 com.spring.sd.BeanFactoryApplication.Config 添加到 Bean 工厂中
        // bean 的定义（class，scope，初始化，销毁）
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class)
                .setScope("singleton")
                .getBeanDefinition();
        beanFactory.registerBeanDefinition("config", beanDefinition);


        // 根据对 @Configuration 和 @Bean 两个注解的认识可知，Bean 工厂中应该还存在 bean1 和 bean2，那为什么现在没有呢？
        // 很明显是现在的 BeanFactory 缺少了解析 @Configuration 和 @Bean 两个注解的能力。
        // 3、给 BeanFactory 添加一些常用的后置处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

        // internalAutowiredAnnotationProcessor解析@Autowired注解
        // internalCommonAnnotationProcessor解析@Resource注解
        // 4、使用后置处理器 BeanFactoryPostProcessor 补充了一些 Bean 的定义
        beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(i -> i.postProcessBeanFactory(beanFactory));

        // 在先前添加到 BeanFactory 中的后置处理器里，有名为 internalAutowiredAnnotationProcessor 和 internalCommonAnnotationProcessor 的两个后置处理器。
        // 前者用于解析 @Autowired 注解，后者用于解析 @Resource 注解，它们都有一个共同的类型 BeanPostProcessor，因此可以
        // 5、要添加Bean后置处理器，对 Bean 的生命周期的各个阶段提供拓展，例如 @AutoWired @Resource...
        // 建立 BeanPostProcessor 和 BeanFactory 的关系后，bean2 被成功注入到 bean1 中了。
        // internalAutowiredAnnotationProcessor 排在 internalCommonAnnotationProcessor 之前。可以查看它们的先后关系
        beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(i -> {
            System.out.println(">>>> " + i);
            beanFactory.addBeanPostProcessor(i);
        });

        // 也可以改变它们的顺序，然后再查看注入的是bean4
        //beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
        //        .sorted(Objects.requireNonNull(beanFactory.getDependencyComparator()))
        //        .forEach(i -> {
        //            System.out.println(">>>> " + i);
        //            beanFactory.addBeanPostProcessor(i);
        //        });

        // 当需要使用 Bean 时，Bean 才会被创建，即按需加载。beanFactory.preInstantiateSingletons();预先就初始化好单例对象
        // 6、预先初始化单例对象（完成依赖注入和初始化流程）
        beanFactory.preInstantiateSingletons();
        System.out.println("---------------------------------------------");
        System.out.println(beanFactory.getBean(Bean1.class).getInter());


    }


    /**
     * 向 Bean 工厂中添加了 bean3 和 bean4，并且计划在 bean1 中注入 Inter 类型的 Bean。
     *
     * 现在 Bean 工厂中 Inter 类型的 Bean 有两个，分别是 bean3、bean4，那么会注入哪一个呢？
     */
    @Configuration
    static class Config {

        @Bean
        public Bean3 bean3() {
            return new Bean3();
        }

        @Bean
        public Bean4 bean4() {
            return new Bean4();
        }

        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }
    }

    interface Inter {

    }

    @Slf4j
    static class Bean3 implements Inter {
        public Bean3() {
            log.info("构造 Bean3()");
        }
    }

    @Slf4j
    static class Bean4 implements Inter {
        public Bean4() {
            log.info("构造 Bean4()");
        }
    }

    @Slf4j
    static class Bean1 {


        /**
         * 如果只使用 @Autowired，首先会按照类型注入，如果同种类型的 Bean 有多个，再按照变量名称注入，如果再注入失败，就报错；
         * 如果只使用 @Resource，也会采取与 @Autowired 一样的注入策略，只不过 @Resource 注解还可以指定需要注入 Bean 的 id（使用 name 属性进行指定），
         * 如果指定了需要注入 Bean 的 id，就直接按照指定的 id 进行注入，如果失败就报错。
         *
         * 那如果即使用 @Autowired 又使用 @Resource(name = "bean4") 呢？
         * 答：根据打印的结果com.spring.sd.BeanPostProcessorSort$Bean3@7d68ef40可知，
         * @Autowired 先生效了，这是因为 internalAutowiredAnnotationProcessor 排在 internalCommonAnnotationProcessor 之前。可以查看它们的先后关系
         */

        @Autowired
        @Resource(name = "bean4")
        private Inter bean3;

        // 如果是存在多个Inter类型的bean，并且没有使用@Qualifier指定哪个bean，那么会根据变量名做为bean名在容器中找，
        // 所以这个还可以注入bean3
        /*@Autowired
        private Inter bean3;*/

        private Inter getInter() {
            return bean3;
        }
    }
}
